#include "CShadersCache.h"
#include "ILDXException.h"
#include "CRenderDriverD3D11.h"

//--------------------------------------------------------------------------------------
// Helper for compiling shaders with D3DX11
//--------------------------------------------------------------------------------------
HRESULT CompileShaderFromFile(WCHAR* szFileName, LPCSTR szEntryPoint, LPCSTR szShaderModel, ID3DBlob** ppBlobOut)
{
	HRESULT hr = S_OK;

	DWORD dwShaderFlags = D3DCOMPILE_ENABLE_STRICTNESS;
#if defined( DEBUG ) || defined( _DEBUG )
	// Set the D3DCOMPILE_DEBUG flag to embed debug information in the shaders.
	// Setting this flag improves the shader debugging experience, but still allows 
	// the shaders to be optimized and to run exactly the way they will run in 
	// the release configuration of this program.
	dwShaderFlags |= D3DCOMPILE_DEBUG;
#endif

	ID3DBlob* pErrorBlob;
	hr = D3DX11CompileFromFile(szFileName, NULL, NULL, szEntryPoint, szShaderModel,
		dwShaderFlags, 0, NULL, ppBlobOut, &pErrorBlob, NULL);
	if (FAILED(hr))
	{
		if (pErrorBlob != NULL)
			OutputDebugStringA((char*)pErrorBlob->GetBufferPointer());
		if (pErrorBlob) pErrorBlob->Release();
		return hr;
	}
	if (pErrorBlob) pErrorBlob->Release();

	return S_OK;
}



ldx::render::CShadersCache::CShadersCache(CRenderDriverD3D11* driver)
	:Driver(driver)
{
}

ldx::render::CShadersCache::~CShadersCache()
{
	clearShaders();
}

//return 0 successed,1 failed,2exist.
int ldx::render::CShadersCache::addShader(mt::wchar * filename, char * entryport, E_SHADER_TYPE type, SShaderMid & mid , mt::uint layeroutcount , D3D11_INPUT_ELEMENT_DESC* descs )
{
	std::wstring wfn(filename);
	if (isShaderExist(wfn))
	{
		mid = getShader(wfn);
		return 2;
	}
	if (0 != compilerShader(filename, entryport, type, mid,layeroutcount,descs))
	{
		mid.Type = EST_UNDEFINEDSHADER;
		return 1;
	}
	mid.Type = type;
	//success
	insertShader(std::wstring(filename), mid);
	return 0;
}

int ldx::render::CShadersCache::addShader(mt::wchar * filename, char * entryport, E_SHADER_TYPE type, void ** outShader, mt::uint layeroutcount , D3D11_INPUT_ELEMENT_DESC* descs )
{
	SShaderMid mid;
	std::wstring wfn(filename);
	if (isShaderExist(wfn))
	{
		mid = getShader(wfn);
		return 2;
	}
	if (1 == compilerShader(filename, entryport, type, mid,layeroutcount,descs))
	{
		mid.Type = EST_UNDEFINEDSHADER;
		return 1;
	}
	//success
	insertShader(std::wstring(filename), mid);
	*outShader = mid.VertexShader;//?
	return 0;
}

ldx::render::SShaderMid ldx::render::CShadersCache::getShader(std::wstring name)
{
	std::unordered_map<std::wstring, SShaderMid>::iterator it = HashMapOfShaders.find(name);
	if (it != HashMapOfShaders.end())
	{
		return (*it).second;
	}
	return SShaderMid();
}

bool ldx::render::CShadersCache::isShaderExist(std::wstring & name)
{
	std::unordered_map<std::wstring, SShaderMid>::iterator it = HashMapOfShaders.find(name);
	if (it != HashMapOfShaders.end())
	{
		return true;
	}
	return false;
}

int ldx::render::CShadersCache::compilerShader(mt::wchar * filename, char * entryport, E_SHADER_TYPE type, SShaderMid & mid,
		int layoutElements, D3D11_INPUT_ELEMENT_DESC* layouts)
{
	SDeviceMap dm = Driver->getDeviceMap();

	// Compile the vertex shader
	HRESULT  hr;
	ID3DBlob* pVSBlob = NULL;
	const char* model = GetShaderModelBasedType(type);
	if (model == nullptr)
		throw os::ILDXException(L"model == nullptr at CShadersCache::compilerShader.");

	hr = CompileShaderFromFile(filename, entryport, model, &pVSBlob);
	if (FAILED(hr))
	{
		throw os::ILDXException(L"The FX file cannot be compiled.  Please run this executable from the directory that contains the FX file.");
		return (int)1;
	}
	// Create the vertex shader,mid.
	hr = -1;
	switch (type)
	{
	case ldx::render::EST_UNDEFINEDSHADER:
		break;
	case ldx::render::EST_VERTEXSHADER:
		hr = dm.Window.D3DDevice->CreateVertexShader(pVSBlob->GetBufferPointer(), pVSBlob->GetBufferSize(), NULL, &mid.VertexShader);
		break;
	case ldx::render::EST_GEOMETRYSHADER:
		break;
	case ldx::render::EST_PIXELSHADER:
		hr = dm.Window.D3DDevice->CreatePixelShader(pVSBlob->GetBufferPointer(), pVSBlob->GetBufferSize(), NULL, &mid.PixelShader);
		break;
	case ldx::render::EST_TCS:
		break;
	case ldx::render::EST_TESS:
		break;
	case ldx::render::EST_CALCULATE:
		break;
	default:
		break;
	}
	if (FAILED(hr))
	{
		pVSBlob->Release();
		return 2;
	}
	if (layoutElements>0)
	{
		// Create the input layout
		hr = dm.Window.D3DDevice->CreateInputLayout(layouts, layoutElements, pVSBlob->GetBufferPointer(),
			pVSBlob->GetBufferSize(), &mid.InputLayout);
		if (FAILED(hr))
		{
			pVSBlob->Release();
			return 3;
		}
	}
	if(pVSBlob)
		pVSBlob->Release();
	return 0;
}

void ldx::render::CShadersCache::insertShader(std::wstring& name,SShaderMid& mid)
{
	if (isShaderExist(name))
	{
		throw os::ILDXException(L"The shader with name " + name + L" has exist!");
		return;
	}
	HashMapOfShaders.insert(std::pair<std::wstring, SShaderMid>(name, mid));
}

void ldx::render::CShadersCache::clearShaders()
{
	std::unordered_map<std::wstring, SShaderMid>::iterator it;
	for (it = HashMapOfShaders.begin(); it != HashMapOfShaders.end(); ++it)
	{
		deleteShader((*it).second);
	}
	HashMapOfShaders.clear();
}

void ldx::render::CShadersCache::deleteShader(SShaderMid& mid)
{
	if (mid.VertexShader!=nullptr)
	{
		switch (mid.Type)
		{
		case EST_VERTEXSHADER:
			mid.VertexShader->Release();
			mid.VertexShader = nullptr;
			break;
		case EST_GEOMETRYSHADER:
			mid.GeometryShader->Release();
			mid.GeometryShader = nullptr;
			break;
		case EST_PIXELSHADER:
			mid.PixelShader->Release();
			mid.PixelShader = nullptr;
				break;
		default:
			std::cout << "CSHadersCache::deleteShader,don't know how to handle this type:" << mid.Type << std::endl;
			throw os::ILDXException(L"CSHadersCache::deleteShader,don't know how to handle the shader type.");
			break;
		}
	}
}
